home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 July: Mac OS SDK / Dev.CD Jul 96 SDK / Dev.CD Jul 96 SDK1.toast / Development Kits (Disc 1) / OpenDoc Development Framework / Documentation / Engineering Notes / Scripting / ODF Scripting Overview
Encoding:
Text File  |  1996-04-26  |  21.7 KB  |  292 lines  |  [TEXT/ttxt]

  1. OpenDoc
  2. Development
  3. Framework
  4.                                                                                                                                                                                                  
  5. ODF Scripting Overview
  6. ODF Release 1                                                                                                                                                                          
  7.  
  8. Table of Contents
  9.  
  10. • Introduction
  11. • Before Getting Started
  12. • Semantic Interfaces
  13. • Scriptable Objects
  14. • AcquireScriptable/ReleaseScriptable
  15. • Scriptable Objects and Frames
  16. • Accessing Contained Elements
  17. • Properties of Scriptable Objects 
  18. • Automatic Undo of SetProperty
  19. • Scriptable Collections
  20. • Descriptor Records
  21. • Apple Events
  22. • Object Specifiers
  23. • Part Terminology
  24.  
  25.  
  26. Introduction
  27.  
  28. Release 1 of the OpenDoc Development Framework introduces support for the creation of scriptable OpenDoc part editors.  Since scripting support is new to ODF, and has not been fully qualifed by the ODF quality team, the following caveats apply:
  29.  
  30.       • API related to scripting will change in future releases of ODF
  31.       • Quality and reliability has not been verified
  32.       • Not all planned scripting, recording, and attaching features are implemented
  33.  
  34. In spite of these caveats, the current ODF scripting implementation is highly functional and has passed early quality inspections.
  35.  
  36.  
  37. Before Getting Started
  38.  
  39. Before getting started, read Chapter 9 of the OpenDoc Programmer’s Guide, “Semantic Events and Scripting”. This chapter provides an overview of how OpenDoc scripting works, and will provide a reference context for the ODF scripting implementation.  This engineering document focuses specifically on the ODF semantic event subsystem and does not attempt to provide a tutorial on scripting support in OpenDoc.
  40.  
  41. Semantic event support in the Macintosh version of OpenDoc is based on the OpenScripting Architecture (OSA) and the Apple Event Manager. This document assumes a working familiarity with OSA concepts and Apple Event Manager data structures such as AEDescs and AppleEvents. A complete reference on this topic is available as “Inside Macintosh: Interapplication Communication,” published by Addison Wesley. Several helpful journal articles are also listed in the section of this document called “Additional Reading Material.”
  42.  
  43.  
  44. Semantic Interfaces
  45.  
  46. Semantic event support in OpenDoc is based on the OpenDoc semantic interface class. When OpenDoc wants to send semantic events to a part, it requests the part’s semantic interface by calling the part’s AcquireExtension method. Scriptable ODF parts automatically create and return an instance of a semantic interface. The ODF semantic interface understands the object hierarchy of ODF parts, and can walk the object tree to locate specific objects. 
  47.  
  48. Semantic events are targeted at specific scriptable objects that live within a part. When OpenDoc receives a semantic event, it first attempts to locate the object or objects at which the event is targeted. Once OpenDoc has resolved the target specifier into an object or a collection of objects, the event is dispatched to the part that contains the target object. During this process the ODF semantic interface acts as an interface between the OpenDoc shell and the objects contained in the target part.
  49.  
  50. ODF provides a fully functional implementation of the OpenDoc Semantic Interface. Scripting can be enabled in  an ODF part by editing its “Defines.k” file.  In its default state,  “Defines.k” contains the following two lines:
  51.  
  52.    #define FW_SUPPORTS_EXTENSIONS     0
  53.    #define FW_SUPPORTS_SCRIPTING     0
  54.  
  55. To enable scripting, edit these lines to read:
  56.  
  57.    #define FW_SUPPORTS_EXTENSIONS     1
  58.    #define FW_SUPPORTS_SCRIPTING     1
  59.  
  60. Building a part with FW_SUPPORTS_SCRIPTING defined as 1 causes ODF to automatically create and return an instance of a Semantic Interface when requested by OpenDoc.  It is necessary to enable support for extensions since ODF’s scripting implementation relies on extension management features enabled by the FW_SUPPORTS_EXTENSIONS definition.
  61.  
  62. The ODF semantic interface is implemented in two parts: a SOM wrapper that is defined in the ODF Shared Library, and a C++ implementation class defined in the Semantic Events subsystem of the ODF Framework layer. When ODF creates a semantic interface, it creates both a wrapper and an implementation object. The wrapper class dispatches directly into the implementation object. The wrapper should never be subclassed or modified. By default, ODF creates an implementation object of class FW_CSemanticInterface. If your part requires a customized subclass of FW_CSemanticInterface, do the following:
  63.  
  64.    1. Declare and implement your subclass of FW_CSemanticInterface
  65.  
  66.    2. Add the following line to your   “Defines.k”  file, replacing   “your_custom_class”
  67.       with the name of your subclass of FW_CSemanticInterface:
  68.  
  69.             #define FW_SEMANTIC_INTERFACE_CLASS  your_custom_class
  70.  
  71.    3. Enable scripting and extensions as explained above.
  72.  
  73.  
  74. Scriptable Objects
  75.  
  76. The Semantic Event subsystem of ODF contains a mixin class, FW_MScriptableObject. Classes that mix FW_MScriptableObject acquire the following behaviors:  1) contained “element” objects can be accessed through the containing scriptable object;  2) properties of the object can be accessed and changed by semantic events; and 3) semantic events can be dispatched directly to the scriptable object once object resolution has been completed. 
  77.  
  78. Scriptable subclasses of FW_CPart should mix in one of the two part-specific subclasses of FW_MScriptable. Embedding parts that are also scriptable should mix FW_MEmbeddingPartScriptable. Non-embedding scriptable parts should mix FW_MPartScriptable. Since the ODF semantic interface makes some assumptions about the behaviors of scriptable parts, it is essential that one of the part-specific subclasses be used in preference to FW_MScriptable.
  79.  
  80. Every scriptable class must have a unique class type constant. AppleScript uses this class type to manage object resolution. When ODF or OpenDoc needs to access the class type of an object, the FW_MScriptable::GetObjectClass method of the object in question is called. GetObjectClass is a virtual method. Subclasses of FW_MScriptable should override this method and return their unique class id.
  81.  
  82.  
  83. Scriptable Objects and Frames
  84.  
  85. Some scripting-related operations require ODF to associate a frame with a scriptable object. Scriptable objects inherit a method called GetFrame from their FW_MScriptable base class that allows them to designate the frame they belong to. By default, this method returns the last active frame of the part. If this default behavior is not appropriate for your scriptable objects, you should override FW_MScriptable::GetFrame and return the correct frame.
  86.  
  87.  
  88. Accessing Contained Elements
  89.  
  90. When OpenDoc receives a semantic event (an Apple event on the Macintosh) it first resolves the target specifier of the event. A target specifier is a tokenized description of a specific object and its relationship to the object hierarchy. In a script, users provide textual descriptions of the objects they want to send events to. AppleScript interprets the textual description, and creates an equivalent tokenized descriptor. An example of an object specifier a user might type into a script is: “shape 1 of part 1”.  Object resolution is a multi-stage process in which OpenDoc walks down the object hierarchy looking for the specified object. At each stage of the traversal, OpenDoc calls the part’s semantic interface asking it to find the next contained element.
  91.  
  92. Classes that mix FW_MScriptable participate in the object resolution process by providing access to the scriptable elements they contain. When ODF wants to access elements of a scriptable object, it calls the GetContainedObject method of the containing object. Parameters passed to GetContainedObject describe the class of the requested object as well as the method used to specify the object and the specification data.  GetContainedObject determines the form used to specify the requested object and calls one of the following accessor methods of FW_MScriptable: GetElementByName, GetAdjacentObject, GetElementsWithinRange,  or GetElementByAbsolutePosition.  GetElementByAbsolutePosition potentially calls one of the following methods of FW_MScriptable: GetFirstElement, GetMiddleElement, GetLastElement, GetAnyElement, GetAllElements, or GetElementByIndex.
  93.  
  94. FW_MScriptable provides default implementations of all of the object accessor methods listed above. The default implementations of these methods rely on the one access-related method container objects must implement : NewElementIterator. NewElementIterator takes, as a parameter, the object class of the element type ODF is trying to access. NewElementIterator is expected to create and return an instance of a subclass of the abstract base class FW_CElementIterator that iterates over the appropriate objects of the specified class. By implementing this one method, and implementing subclasses of FW_CElementIterator for every scriptable container/element relationship, you enable ODF to provide access to elements via the complete range of specifier forms.
  95.  
  96. ODFDraw provides an example of how this method is implemented. 
  97.  
  98.    FW_CElementIterator* CDrawPart::NewElementIterator(Environment* ev,
  99.          FW_CPart* part,
  100.          ODDescType elementType) const
  101.    {
  102.       FW_CElementIterator* iter;
  103.  
  104.       if (elementType == kShapeClass)
  105.          iter = FW_NEW(CSemanticShapeElementIterator, (fPartContent));
  106.       else
  107.          iter = FW_MEmbeddingPartScriptable::NewElementIterator(ev, part, elementType);
  108.    
  109.       return iter;
  110.    }
  111.  
  112. CDrawPart tests the elementType parameter to determine whether or not the type requested is a type contained by Draw. If it is, a shape iterator is created. If it isn’t, the base class is called to provide the iterator. Iterators returned by NewElementIterator must derive from the abstract base class FW_CElementIterator. The First and Next methods of the iterator class return pointers to FW_MScriptable elements.
  113.  
  114. FW_MScriptable's access of contained objects is always done in terms of element iterators.  This default implementation is written to work with any content model and any hierarchy of containers and elements. While this design is flexible and abstract, it is not as optimal as a design implemented with full knowledge of a specific part's content model would be. If your schedule permits, you might want to investigate overriding some or all of the accessors implemented in FW_MScriptable to provide more optimal access based on knowledge of your part's content model.
  115.  
  116.  
  117. Properties of Scriptable Objects
  118.  
  119. Properties of scriptable objects can be tested and set via semantic events. For example, shapes in the ODF Draw example part have a “color” property.  The color of a shape can be tested and set from AppleScript scripts as in the following example:
  120.  
  121.    tell application “ODF Draw Document”
  122.        -- comment: set the color of the first shape to black
  123.       set the color of the first shape to {0, 0, 0}
  124.    end tell
  125.  
  126. FW_MScriptable contains three methods that are called to get and change the property of a scriptable object: HasProperty, GetProperty, and SetProperty. When a scriptable ODF part receives a semantic event requesting a property of a scriptable object, it first calls the object's HasProperty method to verify that the object has the specified property. ODF Draw implements this method in its shape class, as shown below, to indicate that the color property, accessed in the script above, exists:
  127.  
  128.    FW_Boolean CBaseShape::HasProperty(ODDescType whichProperty) const
  129.    {
  130.       FW_Boolean hasProperty;
  131.  
  132.       if (whichProperty == pColor)
  133.          hasProperty = TRUE;
  134.       else
  135.          hasProperty = FW_MScriptable::HasProperty(whichProperty);
  136.  
  137.       return hasProperty;
  138.    }
  139.  
  140. If the call to HasProperty returns TRUE, ODF calls the SetProperty method of the scriptable object, passing in the information necessary to correctly set the property to the new value. The following example illustrates how this method is implemented for a shape in ODF Draw:
  141.  
  142.    void CBaseShape::SetProperty(Environment* ev,
  143.          FW_CPart* part,
  144.          FW_CDesc& propertyValue,
  145.          ODDescType whichProperty)
  146.    {
  147.       if (whichProperty == pColor)
  148.       {
  149.          CDrawPart* drawPart = FW_DYNAMIC_CAST(CDrawPart, part);
  150.          FW_ASSERT(drawPart);
  151.          FW_CColor newColor;
  152.          
  153.          propertyValue >> newColor;
  154.          ChangeFillColor(ev, drawPart, newColor);
  155.          drawPart->GetDrawContent()->RedrawShape(ev, this);
  156.       }
  157.       else
  158.          FW_MScriptable::SetProperty(ev, part, propertyValue, whichProperty);
  159.    }
  160.  
  161. In the example above, CBaseShape::SetProperty first tests to see whether the property being set is the color property. If some other property is specified, the SetProperty method of the FW_MScriptable base class is invoked. If the color is being set, the “part” parameter is dynamically cast from an FW_CPart* to an CDrawPart* (for information on the FW_DYNAMIC_CAST operator, see the ODF documentation on Run-Time Type Identification). Next, the color value is extracted from the propertyValue parameter (see the section in this document on FW_CDesc for an explanation). Once the new color information is extracted, the shape's color is set, and a message is sent to the part's content object requesting a redraw.
  162.  
  163. ODF accesses property values by calling the GetProperty method of the scriptable object being queried. The following example from ODF Draw illustrates how GetProperty is implemented for a shape's color:
  164.  
  165.    FW_Boolean CBaseShape::GetProperty(Environment* ev,
  166.          FW_CPart* part,
  167.          FW_CDesc& propertyValue,
  168.          ODDescType whichProperty,
  169.          ODDescType desiredType) const
  170.    {
  171.       FW_Boolean result;
  172.  
  173.       if (whichProperty == pColor)
  174.       {
  175.          FW_CColor color = fFillInk.GetForeColor();
  176.          propertyValue << color;
  177.          result = TRUE;
  178.       }
  179.       else
  180.          result = FW_MScriptable::GetProperty(ev, part, propertyValue, 
  181.                         whichProperty, desiredType);
  182.    
  183.       return result;
  184.    }
  185.  
  186.  
  187. Automatic Undo of Set Property
  188.  
  189. ODF provides automatic support for undo/redo of SetProperty semantic events. The steps ODF follows to implement automatic undo are as follows:
  190.  
  191.    1. Call the HasProperty method of the target object to verify the existence of the specified property.
  192.    2. Call the GetProperty method of the target object to get the current property value.
  193.    3. Call the GetUndoStrings method of the target object to get the correct undo/redo strings.
  194.    4. Create an undoable/redoable command of type FW_CPropertyCommand.
  195.    5. Execute the command.
  196.  
  197. The only method mentioned here that hasn't already been discussed is FW_MScriptable::GetUndoStrings. Scriptable objects can optionally override GetUndoStrings to provide strings that are specific to the property being changed. If a scriptable object does not override this method, default strings of “Undo Set Property” and “Redo Set Property” will be used. Below is an example of how the CBaseShape in ODF Draw overrides GetUndoStrings to provide strings specific to changing a color:
  198.  
  199.    void CBaseShape::GetUndoStrings(Environment* ev,
  200.          FW_CPart* part,
  201.          ODDescType whichProperty,
  202.          FW_CString& undoString,
  203.          FW_CString& redoString) const
  204.    {
  205.       if (whichProperty == pColor)
  206.       {
  207.          FW_CSharedLibraryResourceFile resFile(ev);
  208.          ::FW_LoadStringByID(ev, resFile, kDrawUndoStrings, 
  209.                   MULTISTRINGRES, kUndoFillColorMsg, undoString);
  210.          ::FW_LoadStringByID(ev, resFile, kDrawUndoStrings,
  211.                   MULTISTRINGRES, kRedoFillColorMsg, redoString);
  212.       }
  213.       else
  214.          FW_MScriptable::GetUndoStrings(ev, part, whichProperty, undoString, redoString);
  215.    }
  216.  
  217. In this example, if the property being set is the color property, undo and redo strings are loaded from the resource fork of the ODF Draw shared library. If the property is not the color property, the FW_MScriptable base class is called to provide the default strings.
  218.  
  219. Since ODF manages the flow of control through this process, the only responsiblity the part writer has is to implement HasProperty, GetProperty, SetProperty, and, optionally, GetUndoStrings, to enable automatic undo of setting of properties.
  220.  
  221.  
  222. Scriptable Collections
  223.  
  224. Some semantic events are targeted at multiple scriptable objects. One example of this type of event is generated by the use of “whose” clauses in AppleScript. A whose clause specifies a collection of objects that satisfy a specific test or tests. An example of a whose-claused-based directive a user might write when scripting ODF Draw follows:
  225.  
  226.    tell application "ODFDraw Document"
  227.       -- comment: set the color of every circle to black
  228.       set the color of every shape whose type is circle to {0, 0, 0}
  229.    end
  230.  
  231. OpenDoc and ODF work together to resolve the complex target specifier “every shape whose type is circle”. The result is a collection of scriptable objects, actually CBaseShapes, that satisfy the “type is circle” test.  Whenever ODF needs to create a collection of scriptable objects, it creates an instance of the class FW_CScriptableCollection. FW_CCollection is a hybrid class: it is both a collection and a scriptable object. This means that every object in the collection must derive from FW_MScriptable. It also means that FW_CScriptableCollection can behave like a scriptable object: it can provide access to its elements, and receive and handle semantic events.  When a semantic event is dispatched to an instance of FW_CScriptableCollection, the collection iterates through its contents dispatching the event to each object in turn.
  232.  
  233.  
  234. Descriptor Records
  235.  
  236. OpenDoc defines a set of classes that are used to pass semantic-event related data into and out of parts. Some of the classes defined by OpenDoc are ODDesc, ODToken, ODAppleEvent and ODObjectSpec.  If you are familiar with the AppleEvent Manager of the Macintosh toolbox, you'll note that the classes defined by OpenDoc closely parallel structures defined by the AppleEvent Manager. The OpenDoc classes are, out of necessity, SOM-based equivalents of the AppleEvent Manager versions.
  237.  
  238. ODF addresses the incompatibility between the OpenDoc and AppleEvent manager versions of descriptor records via a class called FW_CDesc. FW_CDesc can be initialized with either an ODDesc or an AEDesc. Conversion operators exist to provide seamless compatibility with OpenDoc interfaces and with Macintosh toolbox interfaces. In other words, you can pass an FW_CDesc as an AEDesc or an ODDesc and the correct conversions will occur. FW_CDesc uses sophisticated caching to insure that, regardless of how it is passed, data is not stale.
  239.  
  240. In addition to the FW_CDesc interface, ODF provides C++ style insertion and extraction operators for FW_CDesc. These operators are defined as global functions in the source file “FWDscOpr.cp”. In an example that appears earlier in this document, the following lines of code appeared:
  241.  
  242.         
  243.    // property value is an FW_CDesc passed in as a parameter
  244.    FW_CColor newColor;
  245.    propertyValue >> newColor;
  246.  
  247. This example illustrates how a color can be extracted from an AEDesc using one of the descriptor extraction operators. The color could also be extracted as follows:
  248.  
  249.    FW_CColor newColor;
  250.    propertyValue.GetColor(newColor);
  251.  
  252. or
  253.  
  254.    FW_CColor newColor;
  255.    RGBColor platformColor;
  256.    Size actualSize;
  257.    GetDataByPtr(typeRGBColor, &platformColor, actualSize, sizeof(RGBColor));
  258.    newColor = platformColor;
  259.  
  260. FW_CDesc's also contain API to enable them to contain AELists and AERecords. List access is supported via the following methods: GetItemCount, GetDescFromList, and DeleteDescFromList. These methods are self explanatory. To support record access, most methods of FW_CDesc take an optional key parameter. If no key is provided, the default “keyNoKey” parameter is used. This internally defined key is recognized by ODF as an indicator that no key should be used to get or set the specified data.
  261.  
  262.  
  263. Apple Events
  264.  
  265. ODF wraps ODAppleEvents inside of a subclass of FW_CDesc called FW_CAppleEvent. FW_CAppleEvent inherits all of the functionality of FW_CDesc. FW_CAppleEvents have additional API that enables access to AppleEvent attributes. ODF provides built-in accessors for AppleEvent class and ID, as well as for subject attributes.
  266.  
  267. While ODF Release 1 does not provide explicit support for recording of AppleEvents, FW_CAppleEvent contains a sample method called FW_MakeSetPropertyEvent, which was written to provide some insight into how a factored, recording part might be written. This method is not currently called by the framework, and is present specifically for its value as sample code.
  268.  
  269.  
  270. Object Specifiers
  271.  
  272. The Semantic Events subsystem of ODF contains a utility method used in the creation of object specifiers. When creating an object specifer, ODF users should call the FW_CreateObjSpecifier method in preference to the similarly named toolbox method, CreateObjSpecifier. FW_CreateObjSpecifier is functionally equivalent to CreateObjSpecifier, but eliminates the need for a part to link against the Macintosh shared library called ObjectSupportLib.
  273.  
  274.  
  275. Part Terminology
  276.  
  277. OpenDoc parts publish their scripting terminology the same way that applications do. Terminology for a part is contained in an resource of type ‘aete’. ODF does not do anything explicit to effect this process. Information on how to create a terminology resource for your part can be found in the OpenDoc Programmer's Guide.
  278.  
  279.  
  280. Additional Reading Material
  281.  
  282. Inside Macintosh: Interapplication Communication (Addison-Wesley, 1993)
  283.  
  284. “Apple Event Objects and You” by Richard Clark, develop  Issue 10
  285.  
  286. “Better Apple Event Coding Through Objects” by Eric M. Berdahl, develop  Issue 12
  287.  
  288. “Designing a Scripting Implementation” by Cal Simone develop  Issue 21
  289.  
  290.  
  291. © 1993 - 1996 Apple Computer, Inc. All rights reserved.
  292. Apple, the Apple Logo, Macintosh, and OpenDoc are trademarks of Apple Computer, Inc., registered in the United States and other countries.